﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace Weazel.Toad.Application.Controls
{
	/// <summary>
	/// Note on scrolling:
	/// The minimum value of the scrollbar is always 0 and the maximum
	/// value equals the number of the lines in the textbox. The current
	/// value of the scrollbar is a number between minimum and maximum. It should,
	/// however, always be less that max (actually maximum - linesToDisplay). 
	/// There are two reasons for this. First, we avoid blank lines at the bottom 
	/// of the text area. Secondly, we avoid problems with scrolling when the value
	/// of the scrollbar is in the "blank" area (max-lines to max).
  /// 
  /// note 7 nov 06: added a "dummy" line below the actual text output. Fixes
  /// an error where the last line would not be shown in some cases.
	/// </summary>
	public partial class ConsoleOutputTextBox : UserControl
	{
		/// <summary>
		/// Number of lines that can be shown. Depends on the size of the font
		/// aswell as the height of the control.
		/// </summary>
		private int linesToDisplay;

		/// <summary>
		/// The current index line is used to scroll. 
		/// </summary>
		private int currentIndexLine;

		/// <summary>
		/// Internal data storage
		/// </summary>
		private List<string> lines = new List<string>();

    public void Append(char c)
    {
      // remove dummy line
      this.lines.RemoveAt(this.lines.Count - 1);


      if (lines.Count == 0)
        lines.Add("");

      if (c == '\n')
        this.lines.Add("");
      else
        this.lines[this.lines.Count - 1] = this.lines[this.lines.Count - 1] + c.ToString();

      afterInsert();
    }

		/// <summary>
		/// Append a string. Will automatically scroll to the buttom of the
		/// text area.
		/// </summary>
		/// <param name="text">text to append</param>
		public void AppendText(string text)
		{			
			// break the input text into lines
			string[] lines = text.Split(new char[] { '\n' });

      // remove dummy line
      this.lines.RemoveAt(this.lines.Count - 1);

			for (int i = 0; i < lines.Length; i++)
        if(lines[i] != "")
				  this.lines.Add(lines[i]);

      afterInsert();
		}

    private void afterInsert()
    {
      // add dummy line
      this.lines.Add("");

      // update variables used for scrolling
      currentIndexLine = System.Math.Max(this.lines.Count - linesToDisplay - 1, 0);

      vScrollBar1.Minimum = 0;
      vScrollBar1.Maximum = System.Math.Max(this.lines.Count - 1, 0);

      if (this.lines.Count < linesToDisplay)
        vScrollBar1.Value = System.Math.Max(this.lines.Count - 1, 0);
      else
        vScrollBar1.Value = System.Math.Max(this.lines.Count - linesToDisplay - 1, 0);

      // update the content of the text area
      update();
    }

		/// <summary>
		/// Clear the textbox
		/// </summary>
		public void Clear()
		{
			lines.Clear();

			currentIndexLine = 0;

			vScrollBar1.Minimum = 0;
			vScrollBar1.Maximum = linesToDisplay;
			vScrollBar1.Value = 0;

			textBox1.Clear();

      // add dummy line
      this.lines.Add("");
		}

		/// <summary>
		/// Returns the content of this textbox as a string.
		/// </summary>
		/// <returns>Text</returns>
		public string GetAsString()
		{
			StringBuilder text = new StringBuilder();
			foreach (string line in lines)
				text.Append(line + "\n");

			return text.ToString();
		}

		/// <summary>
		/// Place the content of the textbox in the clipboard
		/// </summary>
		public void CopyToClipboard()
		{
			System.Windows.Forms.Clipboard.SetText(GetAsString(), TextDataFormat.Text);
		}

		/// <summary>
		/// Save the content of this textbox to a file
		/// </summary>
		public void SaveToFile()
		{
			System.Windows.Forms.SaveFileDialog dialog = new SaveFileDialog();			
			if (dialog.ShowDialog() == DialogResult.OK)
			{
				System.IO.Stream stream = null; 
				System.IO.StreamWriter writer = null;

				try
				{
					stream = dialog.OpenFile();
					writer = new System.IO.StreamWriter(stream);
					writer.Write(GetAsString());
				}
				finally
				{
					if (writer != null)
						writer.Close();
					if (stream != null)
						stream.Close();
				}
			}
		}

		private void update()
		{
			if (lines.Count < linesToDisplay)
			{
				// show all!
				textBox1.Lines = lines.ToArray();
			}
			else
			{
				// show 'linesToDisplay' lines starting with line at
				// index 'currentIndexLine'

				int index = System.Math.Min(currentIndexLine, System.Math.Max(lines.Count - linesToDisplay - 1, 0));
				textBox1.Lines = lines.GetRange(index, linesToDisplay).ToArray();
			}
		}
		
		private void updateScrollBar()
		{
			vScrollBar1.Minimum = 0;

			if (lines.Count < linesToDisplay)
			{
				vScrollBar1.Enabled = false;
				vScrollBar1.Maximum = 0;
			}
			else
			{
				vScrollBar1.Enabled = true;
				vScrollBar1.Minimum = 0;
				vScrollBar1.Maximum = lines.Count - 1; 
			}
		}

		public ConsoleOutputTextBox()
		{
			InitializeComponent();

			// register necessary event handlers
			vScrollBar1.Scroll += new ScrollEventHandler(vScrollBar1_Scroll);
			textBox1.Resize += new EventHandler(textBox1_Resize);
			textBox1.MouseWheel += new MouseEventHandler(textBox1_MouseWheel);

      // add dummy line
      this.lines.Add("");
		}

		/// <summary>
		/// Handles 'mousewheel" events. Each activation of the mousewheel 
		/// will make the textbox scroll 3 lines.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void textBox1_MouseWheel(object sender, MouseEventArgs e)
		{			
			if (e.Delta < 0) // get scroll direction
			{
				int value = 				 
					System.Math.Min(
						vScrollBar1.Maximum - linesToDisplay - 1, 
						vScrollBar1.Value + 3
					);

				// scroll value must be atleast 0
				vScrollBar1.Value = System.Math.Max(0, value);
			}
			else
			{
				int value = 
					System.Math.Max(
						vScrollBar1.Minimum, 
						vScrollBar1.Value - 3
					);

				// scroll value must exceed maximimum
				vScrollBar1.Value = System.Math.Min(vScrollBar1.Maximum, value);
			}

			// Setting the Value will not cause a scroll event
			// so update manually ..
			updateCurrentIndexLine(vScrollBar1.Value);			
		}

		private void updateCurrentIndexLine(int scrollBarValue)
		{
			currentIndexLine = scrollBarValue;
			update();
		}

		void vScrollBar1_Scroll(object sender, ScrollEventArgs e)
		{
			updateCurrentIndexLine(e.NewValue);
		}

		void textBox1_Resize(object sender, EventArgs e)
		{		
			linesToDisplay = textBox1.Height / ( textBox1.Font.Height + 3);
			currentIndexLine = 0;
      update();
		}

		private void copytoClipboardToolStripMenuItem_Click(object sender, EventArgs e)
		{
			CopyToClipboard();
		}

		private void saveContenttoFileToolStripMenuItem_Click(object sender, EventArgs e)
		{
			SaveToFile();
		}
	}
}
